package com.me.myFramework.aop22.aop124;

import com.me.myFramework.aop22.aop123.intercept.method.ReflectiveMethodInvocation123;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ReflectiveMethodInvocation;
import org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor;
import org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
 * 《AOP-124》：  <br><br>
 *
 * TODO 以下变更点：<ol>
 *     <li>
 *          使用 Spring ReflectiveMethodInvocation 替换自定义的 {@link ReflectiveMethodInvocation123 ReflectiveMethodInvocation}
 *     </li>
 *     <li>
 *          由于 {@link org.springframework.aop.framework.ReflectiveMethodInvocation Spring ReflectiveMethodInvocation} 构造函数访问修饰符的限定，无法直接调用，所以通过子类公开一个构造函数
 *     </li>
 *  </ol>
 */
public class ProxyFactory124 {

    private final Object target;
    private final List<Advice> advices = new ArrayList<>();

    public ProxyFactory124(Object target) {
        this.target = target;
    }

    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), (proxy, method, args) -> {
            List<Object> interceptors = getInterceptors();
            Object retVal;
            if (interceptors.size() > 0) {
                // 获取 ReflectiveMethodInvocation 构造函数
                Class<?> clazz = ReflectiveMethodInvocation.class;
                Constructor<?> constructor = clazz.getDeclaredConstructor(
                        Object.class,          // proxy
                        Object.class,          // target
                        Method.class,          // method
                        Object[].class,        // arguments
                        Class.class,           // targetClass
                        List.class             // interceptorsAndDynamicMethodMatchers
                );
                constructor.setAccessible(true);
                // 使用反射调用构造函数
                MethodInvocation invocation = (ReflectiveMethodInvocation) constructor.newInstance(
                        proxy, target, method, args, target.getClass(), interceptors
                );
                // MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, interceptors);
                retVal = invocation.proceed();
            }
            else {
                return method.invoke(target, args);
            }

            return retVal;
        });
    }

    /**
     * 筛选出当前正在执行的方法适用的 advice（切面），并转换成 拦截器（执行增强逻辑的执行器）。
     * 逻辑类似 {@link org.springframework.aop.framework.AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice(java.lang.reflect.Method, Class) AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice}
     *
     * @return  匹配当前方法的拦截器
     */
    private List<Object> getInterceptors() {
        List<Object> interceptorList = new ArrayList<>(this.advices.size());
        for (Advice advice : this.advices) {
            if (advice instanceof MethodBeforeAdvice) {
                interceptorList.add(new MethodBeforeAdviceInterceptor((MethodBeforeAdvice) advice));
            } else if (advice instanceof AfterReturningAdvice) {
                interceptorList.add(new AfterReturningAdviceInterceptor((AfterReturningAdvice) advice));
            } else {
                // 实际是不存在这种情况的，因为 Advice 的子接口是自己定义的，也都会在这边判断
                throw new RuntimeException("GG, 未知的 Advice");
            }
        }

        return interceptorList;
    }

    public void addAdvice(Advice advice) {
        this.advices.add(advice);
    }

    private boolean isCustomMethod(String methodName) {
        return !("toString".equals(methodName) || "hashCode".equals(methodName) || "equals".equals(methodName)
                || "clone".equals(methodName));
    }

}
